from abc import abstractmethod
from dataclasses import dataclass
from datetime import datetime
from typing import Sequence, List, Union, Dict

from emi.bar.data import BarData
from emi.core.Context import Context
from emi.trade.data import AccountData, PositionData, Direction, DailyResult, OpType, OrderData, Offset, TradeData
from emi.util import utils




class Portfolio:


    """
        账面资产对象。
    """
    def __init__(self,engine,init_captital):
        from emi.trade.trader_engine import ITradeMarket
        from emi.trade.trader_engine import ExchangeEngine
        engine:ExchangeEngine = engine
        self.init_capital = init_captital
        self.exechange_engine = engine
        self.default_bar_markets:ITradeMarket = None
        self._broker_defaut_source = None

    def get_portfolio_data(self):
        return self.exechange_engine.portfolio

    @property
    def available(self)->float:
        """
         ##可用资金
        """
        return self.exechange_engine.portfolio.available

    @property
    def balance(self) -> float:
        """
         ##当前市值。
        """
        return self.exechange_engine.portfolio.balance

    @property
    def account_data(self)->AccountData:
        """
        资金账户情况
        """
        return self.exechange_engine.portfolio.to_account_data()

    def set_default_bar_realtime(self, markets):
        if markets is None:
            return
        self.default_bar_markets = markets

    ### market驱动

    def _makret_before_open(self,time:datetime):
        """
        准备新的一天的资产,生成一个新的日报，将进行资产状态重置工作。
        """
        self.exechange_engine.on_day_begin(time)


    def _on_trade_time_go(self, time: datetime):
        """
        交易时间流逝。
        """
        self.update_orders(time)


    def _market_after_close(self, time:datetime, markets):
        """
        清算今天的账号数据。也就收市之后的动作。
        """
        self.update_balance(time,markets)
        self.exechange_engine.on_day_end(time)


    def update_orders(self, time:datetime, marekt=None):
        """
        更新账面里的订单，并撮合订单成交。 调用频率比较高，需要在tick行情发生时调用。
        """
        market = self.default_bar_markets if marekt is None else marekt

        self.exechange_engine.process_tick_data(market,time)

    def update_balance(self, time:datetime, marekt=None):
        """
        更新账面持仓市值数据
        """
        market = self.default_bar_markets if marekt is None else marekt
        self.exechange_engine.update_position_balance(time, market)

    def update_position(self, symbol:str, marekt = None)->PositionData:
        """
        更新仓位价格
        """
        market = self.default_bar_markets if marekt is None else marekt

        if market is None:
             raise RuntimeError("没有实时行情")

        position = self.getPosition(symbol)
        if self.exechange_engine.update_single_position_price(position,market):
            return position
        return None

    def get_all_symbols(self,is_long = True)->Sequence[str]:
        """
        返回持有的个股symbol列表。
        """
        direction =  Direction.LONG if is_long else Direction.SHORT
        pos_list = self.exechange_engine.portfolio.get_all_positions(direction)
        symbol_list = []
        for pos in pos_list:
            if pos.volume > 0.01:
                symbol_list.append(pos.symbol)
        return symbol_list

    def get_available_volume(self, symbol:str)->float:
        """
        返回个股可用的仓位。
        """
        positionData = self.getPosition(symbol,is_long=True)
        return positionData.volume_available

    def getOrders(self,symbol:str = None,direction:Direction = Direction.LONG)->Sequence[OrderData]:
        """
        返回账号里面的未完成订单情况
        """
        order_list = []
        for order in self.exechange_engine.portfolio.orders:
            order:OrderData = order
            if order.symbol == symbol and order.direction == direction:
                order_list.append(order)
        return order_list

    def get_all_position(self, is_long=True) -> Sequence[PositionData]:
        """
        返回所有个股持仓情况
        """
        direction = Direction.LONG if is_long else Direction.SHORT
        return self.exechange_engine.portfolio.get_all_positions(direction)


    def getPosition(self, symbol: str, is_long=True) -> PositionData:
        """
        返回个股持仓情况
        """
        return self.exechange_engine.portfolio.get_position(symbol,Direction.LONG if is_long else Direction.SHORT)

    def get_history_daily_results(self)->List[DailyResult]:
        """
        返回每日日报的历史数据。以便统计。
        """
        return self.exechange_engine.portfolio.daily_results



class Broker:

    """
    证券经纪人，勇于买卖股票
    """
    @staticmethod
    def of(portofolio: Portfolio,source:str = None):
        source = portofolio._broker_defaut_source if source is None else source
        return Broker(portofolio, source)

    def __init__(self,portofolio:Portfolio,default_source = None):
        self.portofolio = portofolio
        self.default_source = default_source  ##
        self.engine = self.portofolio.exechange_engine

    def _get_now(self):
        return self.engine.now_time()

    def can_buy_volume(self,money:float,price:float)->int:
        """
        返回可以买入的最大仓位值
        money: 资金
        price：单价
        """
        one_volume_money = self.engine.portfolio.get_balance_money(price,1)  # 1手所需资金
        one_volume_commission = self.engine.portfolio.get_commission(price,1)  # 1 手成交费用
        can_buy_volumes = money / (one_volume_money + one_volume_commission)
        return int(can_buy_volumes)


    def buy(self,symbol:str, price: float, volume: int = None,msg:str= None,source:any = None,opType:OpType = OpType.UNKONW,immediately:bool=False) -> OrderData:
        """
        Send buy order to open a long position.
        param:
            volume: 为None时，表示用剩余的资金买
            msg:备注消息
            opType: 操作类型
        """
        if volume is None:
            volume = self.can_buy_volume(self.portofolio.available, price)
        assert volume > 0
        r_order = self.engine.build_request_order(symbol,price,volume,opType,source=self.default_source,immediately = immediately)
        r_order.direction = Direction.LONG
        r_order.offset = Offset.OPEN
        r_order.create_time = self._get_now()
        r_order.msg = msg
        r_order.source = source
        return self.engine.send_order_request(r_order)

    def buy_by_money(self,symbol:str, total_money:float,price:float,msg:str= None,source:any = None,opType:OpType = OpType.UNKONW,immediately:bool=False) -> OrderData:
        """
        Send buy order to open a long position.
        param:
            volume: 为None时，表示用剩余的资金买
            msg:备注消息
            opType: 操作类型。
        """
        volume = self.can_buy_volume(total_money,price)
        assert volume > 0
        if price is None:
            ###市价买入
            position = self.portofolio.update_position(symbol)
            if position is None:
                self.engine.log(f"下buy_by_money订单失败,{symbol}可能没开盘，无法获取实时价格")
                return
            price = position.price_cur
        r_order = self.engine.build_request_order(symbol,price,volume,opType,source=self.default_source,immediately=immediately)
        r_order.direction = Direction.LONG
        r_order.offset = Offset.OPEN
        r_order.create_time = self._get_now()
        r_order.msg = msg
        r_order.source = source
        return self.engine.send_order_request(r_order)

    def sell(self, symbol:str,price: float = None, volume: int = None,msg:str= None,immediately:bool = False,opType:OpType = OpType.UNKONW) -> OrderData:
        """
        Send sell order to close a long position.
        price == None： 现价卖出。
        """
        if volume is None:
            volume = self.portofolio.get_available_volume(symbol)
        assert  volume > 0
        if price is None:
            ###市价卖出
            position = self.portofolio.update_position(symbol)
            if position is None:
                self.engine.log(f"下sell订单失败,{symbol}可能没开盘，无法获取实时价格")
                return
            price = position.price_cur
        r_order = self.engine.build_request_order(symbol,price,volume,opType,source=self.default_source,immediately=immediately)
        r_order.direction = Direction.LONG
        r_order.offset = Offset.CLOSE
        r_order.create_time = self._get_now()
        r_order.msg = msg
        return self.engine.send_order_request(r_order)

    def sell_all(self, position: PositionData, price: float = None,msg:str= None,opType:OpType = OpType.UNKONW,immediately:bool = False) -> OrderData:
        if position.volume_frozen > 0.1:
            raise RuntimeError(f"{position.symbol} 不能清仓，有仓位被冻结: {position.volume_frozen}")

        return self.sell(position.symbol,price,volume=position.volume,msg = msg,opType=opType,immediately=immediately)

    def short(self, symbol:str,price: float, volume: float,msg:str= None,source:any=None,opType:OpType = OpType.UNKONW,immediately:bool=False) -> OrderData:
        """
        Send short order to open as short position.
        """

        r_order = self.engine.build_request_order(symbol,price,volume,opType,source=self.default_source,immediately=immediately)
        r_order.direction = Direction.SHORT
        r_order.offset = Offset.OPEN
        r_order.create_time = self._get_now()
        r_order.msg = msg
        r_order.source = source
        return self.engine.send_order_request(r_order)

    def cover(self,symbol:str, price: float, volume: float,msg:str= None,opType:OpType = OpType.UNKONW,immediately:bool=False)-> OrderData:
        """
        Send cover order to close a short position.
        """
        r_order = self.engine.build_request_order(symbol,price,volume,opType,source=self.default_source,immediately=immediately)
        r_order.direction = Direction.SHORT
        r_order.offset = Offset.CLOSE
        r_order.create_time = self._get_now()
        r_order.msg = msg
        return self.engine.send_order_request(r_order)

    def stop(self, order_id:str):
        """
        撤销订单
        """
        self.engine.send_cancel_request(order_id,syomol=None)
        self.engine._handle_cancel_orders(self.engine.now_time())

    def stop_all(self,syombol= None,source = None):
        """
        撤销所有订单
        """
        if not self.default_source is None:
            if not source is None and source != self.default_source:
                raise RuntimeError("已经强制指定了source，不能传其他source")
            source = self.default_source
        self.engine.send_cancel_request(order_id=None,syomol=syombol,source = source)
        self.engine._handle_cancel_orders(self.engine.now_time())

    # def get_position_orders(self, position: PositionData) -> Sequence[TradeData]:
    #     """
    #     返回当前持仓的买入记录。
    #     """
    #     raise NotImplementedError()




@dataclass
class PortfolioData:
    """
    当前账面上的资产数据。
    """
    available:float  ##可用资金

    commission:float = 0   ## 成交过程中一天产生的总费用，只作为每天的记录，不纳入任何市值统计

    orders:List[OrderData] = None   ## 账面上处理中的(提交中状态）订单，会冻结资金

    long_positions:Dict[str,PositionData] = None   ## 账面上做多持仓情况，浮动市场

    short_positions:Dict[str,PositionData]  = None   ## 账面上做空持仓情况，浮动市场

    today_daily_result: DailyResult = None   ## 当日的日报情况,用于汇总。

    daily_results:List[DailyResult] = None  ##  历史每日成交日报。

    params:{} = None   ##  构建参数，json值

    def __str__(self):
        return f"PortfolioData:[balance:{self.balance},available:{self.available},commission:{self.commission},order_fronzen:{self.get_balance_from_orders()},order_float:{self.get_balance_from_positon()}]"

    @staticmethod
    def of(**kwargs):
        """
        params = {
            "init_capital": 100000000,  ##初始化资金
            "commission_rate": 8 / 10000,  ##成交交易费用
            "one_hand_size": 100,  ## 一手的数量
        }
        """
        init_capital = kwargs['init_capital']
        portfolio_data = PortfolioData(available=init_capital)  ## 账面资产情况
        portfolio_data._init_params(**kwargs)
        return portfolio_data

    @staticmethod
    def deserilize(data: bytes):
        raise NotImplementedError()

    def __post_init__(self):
        if self.params is None:
            self.params = {}
        if self.long_positions is None:
            self.long_positions = {}
        if self.short_positions is None:
            self.short_positions = {}
        if self.orders is None:
            self.orders = []
        if self.daily_results is None:
            self.daily_results = []
        self.__COMMISSION_RATE = 0.003
        self.__ONE_HAND_SIZE = 100

    def serialize(self) -> bytes:
        raise NotImplementedError()

    @property
    def COMMISSION_RATE(self)->float:
        return self.__COMMISSION_RATE

    @property
    def ONE_HAND_SIZE(self)->int:
        return self.__ONE_HAND_SIZE

    @property
    def init_capital(self) -> int:
        return self.params['init_capital']

    def _init_params(self,**kwargs):
        commission_rate = kwargs.get("commission_rate")
        if not commission_rate is None:
            self.__COMMISSION_RATE = commission_rate
        one_hand_size = kwargs.get("one_hand_size")
        if not one_hand_size is None:
            assert isinstance(one_hand_size, int)
            assert one_hand_size > 0
            self.__ONE_HAND_SIZE = one_hand_size
        self.params = kwargs

    def print_position(self):
        """
        打印持仓情况
        """
        print(f"direction|symbol|可用vol|冻结vol")
        for position in self.long_positions.values():
            position: PositionData = position
            print(f"{Direction.LONG}|{position.symbol}|{position.volume_available}|{position.volume_frozen}")
        for position in self.short_positions.values():
            position: PositionData = position
            print(f"{Direction.SHORT}|{position.symbol}|{position.volume_available}|{position.volume_frozen}")

    def resetWithNewDay(self, time):
        """
        新的一天，重置仓位状态
        """
        assert len(self.orders) == 0
        self.commission = 0
        ## 重置仓位
        for position in self.long_positions.values():
            position: PositionData = position
            if position.volume < 0.1:
                continue
            assert not utils.is_same_day(position.update,time)
            assert position.direction == Direction.LONG
            position.volume_available += position.volume_frozen
            position.volume_frozen = 0
            position.update = time
            position.duration +=1

        for position in self.short_positions.values():
            position: PositionData = position
            if position.volume < 0.1:
                continue
            assert not utils.is_same_day(position.update,time)
            assert position.direction == Direction.SHORT
            position.volume_available += position.volume_frozen
            position.volume_frozen = 0
            position.update = time
            position.duration +=1


    @property
    def balance(self) -> float:
        """
        目前账面市值
        """
        money = 0.0
        money += self.get_balance_from_orders()  ## 订单被冻结的资金市值
        money += self.get_balance_from_positon()  ## 持仓情况的资金浮动市值
        money += self.available
        return money

    def get_position(self,symobl:str,direction: Direction = Direction.LONG)->PositionData:
        if direction == Direction.LONG:
            position:PositionData  =  self.long_positions.get(symobl)
        else:
            position:PositionData = self.short_positions.get(symobl)

        if position is None:
            position = PositionData(symbol=symobl,direction=direction)
            if direction == Direction.LONG:
                self.long_positions[symobl] = position
            else:
                self.short_positions[symobl] = position
        return position

    # def get_all_positions(self,direction:Direction = Direction.LONG)->Sequence[PositionData]:
    #     if direction == Direction.LONG:
    #         return list(self.long_positions.values())
    #     else:
    #         return list(self.short_positions.values())

    def get_all_positions(self, director: Direction)->Sequence[PositionData]:
        """
        返回当前持有的筹码
        """
        if director == Direction.LONG:
            pos_list = []
            for position in self.long_positions.values():
                position: PositionData = position
                if position.volume > 0:
                    pos_list.append(position)
            return pos_list
        else:
            assert director == Direction.SHORT
            pos_list = []
            for position in self.short_positions.values():
                position: PositionData = position
                if position.volume > 0:
                    pos_list.append(position)
            return pos_list

    def get_position_balance(self,position:PositionData):
        return  self.get_balance_money(position.price_cur, position.volume)


    def get_balance_from_positon(self) -> float:
        """
        返回持仓情况的是市值，属于浮动值。
        """
        money = 0.0
        for position in self.long_positions.values():
            position: PositionData = position
            assert  position.direction == Direction.LONG
            money +=  self.get_position_balance(position)
        for position in self.short_positions.values():
            position: PositionData = position
            assert  position.direction == Direction.SHORT
            money +=   self.get_position_balance(position)
        return money



    def get_balance_from_orders(self) -> float:
        """
        返回订单里面的市值，属于被冻结的固定金额资金
        """
        forzen_moeny = 0.0
        for order in self.orders:
            order:OrderData = order
            assert not order.is_finished()
            forzen_moeny += order.frozen_money

        return forzen_moeny

    def get_commission(self,price:float,volume:float):
        """
        返回成交所需要的佣金
        """
        return self.COMMISSION_RATE * price * (volume * self.ONE_HAND_SIZE)

    def get_balance_money(self, price:float, volume:float):
        """
        返回市值
        """
        return  price * volume * self.ONE_HAND_SIZE

    def to_account_data(self) -> AccountData:
        data =  AccountData(balance=self.balance, available=self.available)
        data._commission = self.commission  ##
        return data

    def update_posisiton(self, engine,trade_order:TradeData):
        """
        根据成交记录更新仓位状态。
        """
        if trade_order.direction == Direction.LONG:
            if trade_order.offset == Offset.OPEN:
                ## 新增仓位
                position = self.get_position(trade_order.symbol,trade_order.direction)
                before_vol = position.volume
                position.volume_frozen += trade_order.volume
                position.price_cur = trade_order.price
                position.update = trade_order.time
                if trade_order.source:
                    position.source = trade_order.source
                if before_vol > 0.1:
                    ## 摊薄买入价
                    ## 增仓
                    before_money_value = before_vol * position.price_in
                    new_price_in = (trade_order.volume * trade_order.price + before_money_value) / position.volume
                    position.price_in = new_price_in
                    engine.log(
                        f"  增仓：symobl = {position.symbol},direct={trade_order.direction},price:{trade_order.price},volume:{position.volume},before_vol:{before_vol},msg:{trade_order.msg}")
                else:
                    position.price_in = trade_order.price
                    position.create = trade_order.time
                    position.duration = 0
                    engine.log(
                        f"  开仓：symobl = {position.symbol},direct={trade_order.direction},price:{trade_order.price},volume:{position.volume},before_vol:{before_vol},msg:{trade_order.msg}")


            elif trade_order.offset == Offset.CLOSE:
                ## 减去仓位
                position = self.get_position(trade_order.symbol, trade_order.direction)
                before_vol = position.volume

                position.volume_frozen -= trade_order.volume
                position.price_cur = trade_order.price
                if position.volume < 0.1:
                    engine.log(f"  清仓：symobl = {position.symbol},direct={trade_order.direction},price:{trade_order.price},volume:{position.volume},before_vol:{before_vol},msg:{trade_order.msg}")
                else:
                    engine.log(f"  减仓：symobl = {position.symbol},direct={trade_order.direction},price:{trade_order.price},volume:{position.volume},before_vol:{before_vol},msg:{trade_order.msg}")

            else:
                raise NotImplementedError()
        else:
            raise NotImplementedError()




@dataclass
class CancelRequest:

    orderid: str
    symbol: str
    source:str = None

